package thirdpay

import (
	"context"
	"encoding/json"
	"errors"
	"gitee.com/jokces/kit/global/glconf"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/alipay"
	"github.com/go-pay/gopay/wechat/v3"
	"time"
)

// 支付场景
const (
	H5PayScene    string = "H5"
	CodePayScene  string = "CODE"
	AppPayScene   string = "APP"
	JsApiPayScene string = "JSAPI" //微信JSAPI
	MiniPayScene  string = "Mini"  //微信小程序

	PayAli   string = "1"
	PayWx    string = "2"
	PayLine  string = "3"
	PayAdmin string = "4"
	PayOther string = "5"

	WxSUCCESS    string = "SUCCESS"    //微信 支付成功
	WxREFUND     string = "REFUND"     //转入退款
	WxNOTPAY     string = "NOTPAY"     //未支付
	WxCLOSED     string = "CLOSED"     //已关闭
	WxREVOKED    string = "REVOKED"    //已撤销（付款码支付）
	WxUSERPAYING string = "USERPAYING" //用户支付中（付款码支付）
	WxPAYERROR   string = "PAYERROR"   //支付失败(其他原因，如银行返回失败)

	AliWAIT_BUYER_PAY string = "WAIT_BUYER_PAY" //    支付宝 交易创建，等待买家付款。
	AliTRADE_CLOSED   string = "TRADE_CLOSED"   //     未付款交易超时关闭，或支付完成后全额退款。
	AliTRADE_SUCCESS  string = "TRADE_SUCCESS"  //     交易支付成功。
	AliTRADE_FINISHED string = "TRADE_FINISHED" //    交易结束，不可退款。

)

type Pay struct {
	AP  *alipay.Client
	WP  *wechat.ClientV3
	cfg glconf.ThirdPaysConf
}

func NewThirdPay(cfg glconf.ThirdPaysConf) *Pay {
	rs := &Pay{
		cfg: cfg,
	}
	if cfg.Enable && cfg.Al.Enable {
		//    初始化支付宝客户端
		//    appId：应用ID
		//    privateKey：应用私钥，支持PKCS1和PKCS8
		//    isProd：是否是正式环境
		client, err := alipay.NewClient(cfg.Al.AppId, cfg.Al.AppId, cfg.Al.IsProd)
		if err != nil {
			panic("initiate alipay init err: " + err.Error())
			return nil
		}
		//配置公共参数
		client.SetCharset("utf-8").
			SetSignType(alipay.RSA2).
			SetNotifyUrl(cfg.Al.Notify)
		rs.AP = client
	}

	if cfg.Enable && cfg.Wx.Enable {
		// mchid：商户ID 或者服务商模式的 sp_mchid
		// serialNo：商户API证书的证书序列号
		// apiV3Key：APIv3Key，商户平台获取
		// privateKey：商户API证书下载后，私钥 apiclient_key.pem 读取后的字符串内容
		client, err := wechat.NewClientV3(cfg.Wx.MchId, cfg.Wx.SerialNo, cfg.Wx.ApiV3Key, cfg.Wx.PrivateKey)
		if err != nil {
			panic("initiate wx pay init err: " + err.Error())
			return nil
		}
		rs.WP = client
	}
	return rs
}

type PayResp struct {
	PrepayId string `json:"prepayId,omitempty"` //预支付的ID 微信独有
	SignBody string `json:"signBody,omitempty"` //支付宝，微信返回的签名参数
	CodeUrl  string `json:"codeUrl,omitempty"`  //二维码地址 微信独有
	H5Url    string `json:"h5Url,omitempty"`    //h5的请求地址 微信独有
}

type QueryNotify struct {
	OrderId      string `json:"orderId"`      //支付订单号
	TradeStatus  string `json:"tradeStatus"`  //交易状态
	CallBody     string `json:"callBody"`     //回调的JSON参数
	TimeOutCheck bool   `json:"timeOutCheck"` //是否开启超时的检查处理， true：表示需要开启的处理
}

// PreOrder 预支付订单
type PreOrder struct {
	Id            int64  `json:"id"`      //编号
	OrderId       string `json:"orderId"` //订单编号
	PayType       string `json:"payType"` //支付方式：1-支付宝；2-微信支付;3-线下支付；4-管理直接通过;5-其他
	PayScene      string `json:"payScene"`
	OrderTitle    string `json:"orderTitle"` //支付的场景
	RealFee       int64  `json:"realFee"`    //实际支付金额：分
	NotifyUrl     string `json:"notifyUrl"`
	Attach        string `json:"attach"`
	OpenId        string `json:"openId"`
	PayerClientIp string `json:"payerClientIp"`
}

func (p *Pay) ThirdOrderCreate(ctx context.Context, req *PreOrder) (*PayResp, error) {
	rs := &PayResp{}

	//检查支付环境
	if req.PayType == PayAli {
		if !p.cfg.Al.Enable {
			rs.SignBody = "00000000000000000"
			return rs, nil
		}
		bm := make(gopay.BodyMap)
		bm.Set("subject", req.OrderTitle)
		bm.Set("out_trade_no", req.OrderId)
		bm.Set("total_amount", req.RealFee)
		bm.Set("time_expire", timeAddAndFormat(p.cfg.ExpTime))
		if req.NotifyUrl == "" {
			bm.Set("notify_url", p.cfg.Al.Notify)
		}
		switch req.PayScene {
		case CodePayScene:
			url, err := p.AP.TradePagePay(ctx, bm)
			if err != nil {
				return rs, err
			}
			rs.SignBody = url
			return rs, nil
		case H5PayScene:
			url, err := p.AP.TradeWapPay(ctx, bm)
			if err != nil {
				return rs, err
			}
			rs.SignBody = url
			return rs, nil
		case AppPayScene:
			url, err := p.AP.TradeAppPay(ctx, bm)
			if err != nil {
				return rs, err
			}
			rs.SignBody = url
			return rs, nil
		}
		return rs, errors.New("不支持的支付场景")
	}

	if req.PayType == PayWx {
		if !p.cfg.Wx.Enable {
			rs.SignBody = "11111111111111"
			return rs, nil
		}

		bm := make(gopay.BodyMap)
		bm.Set("appid", p.cfg.Wx.AppId)
		bm.Set("mchid", p.cfg.Wx.MchId)
		if req.NotifyUrl == "" {
			bm.Set("notify_url", p.cfg.Wx.Notify)
		}
		bm.Set("description", req.OrderTitle)
		bm.Set("out_trade_no", req.OrderId)
		bm.Set("notify_url", req.NotifyUrl)
		bm.Set("attach", req.Attach)
		bm.Set("time_expire", timeAddAndFormatWx(p.cfg.ExpTime))

		//金额信息
		bm.SetBodyMap("amount", func(b gopay.BodyMap) {
			b.Set("total", req.RealFee)
			b.Set("currency", "CNY")
		})

		//结算信息目前不处理

		//票据信息不处理

		switch req.PayScene {
		//JSAPI/小程序下单API
		//JSAPI支付适用于线下场所、公众号场景和PC网站场景。
		//JSAPI:需要有OPEN_ID
		case JsApiPayScene:
			bm.SetBodyMap("payer", func(b gopay.BodyMap) {
				b.Set("openid", req.OpenId)
			})
			if err := p.wxJsApi(ctx, bm, rs); err != nil {
				return rs, err
			}
			return rs, nil
		case MiniPayScene:
			if err := p.wxJsApi(ctx, bm, rs); err != nil {
				return rs, err
			}
			return rs, nil
		case CodePayScene:
			if err := p.wxCodeApi(ctx, bm, rs); err != nil {
				return rs, err
			}
			return rs, nil
		case H5PayScene:
			//场景信息 H5为必填
			sceneInfo := make(gopay.BodyMap)
			sceneInfo.Set("payer_client_ip", req.PayerClientIp)
			h5Object := make(gopay.BodyMap)
			h5Object.Set("type", "Wap") //H5的支付处理
			sceneInfo.Set("h5_info", h5Object)
			bm.Set("sceneInfo", sceneInfo)
			if req.PayerClientIp == "" {
				return rs, errors.New("ip address need")
			}
			if err := p.wxH5Api(ctx, bm, rs); err != nil {
				return rs, err
			}
			return rs, nil
		case AppPayScene:
			if err := p.wxAppApi(ctx, bm, rs); err != nil {
				return rs, err
			}
			return rs, nil
		}
	}
	return rs, errors.New("不支持的支付场景")
}

// wxJsApi 必须要有OPEN_ID的处理
// openid 必填的处理
// https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/direct-jsons/jsapi-prepay.html
func (p *Pay) wxJsApi(ctx context.Context, bm gopay.BodyMap, out *PayResp) error {
	if bm.Get("openid") == "" {
		return errors.New("openid required")
	}
	rsp, err := p.WP.V3TransactionJsapi(ctx, bm)
	if err != nil {
		return err
	}
	out.SignBody = rsp.SignInfo.SignBody
	out.PrepayId = rsp.Response.PrepayId
	return nil
}

// wxCodeApi 二维码native支付
// https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
func (p *Pay) wxCodeApi(ctx context.Context, bm gopay.BodyMap, out *PayResp) error {
	rsp, err := p.WP.V3TransactionNative(ctx, bm)
	if err != nil {
		return err
	}
	out.SignBody = rsp.SignInfo.SignBody
	out.CodeUrl = rsp.Response.CodeUrl
	return nil
}

// https://pay.weixin.qq.com/docs/merchant/apis/h5-payment/direct-jsons/h5-prepay.html
func (p *Pay) wxH5Api(ctx context.Context, bm gopay.BodyMap, out *PayResp) error {
	bm.Get("scene_info")

	rsp, err := p.WP.V3TransactionH5(ctx, bm)
	if err != nil {
		return err
	}
	out.SignBody = rsp.SignInfo.SignBody
	out.H5Url = rsp.Response.H5Url
	return nil
}

// https://pay.weixin.qq.com/docs/merchant/apis/in-app-payment/direct-jsons/app-prepay.html
func (p *Pay) wxAppApi(ctx context.Context, bm gopay.BodyMap, out *PayResp) error {
	rsp, err := p.WP.V3TransactionApp(ctx, bm)
	if err != nil {
		return err
	}
	out.SignBody = rsp.SignInfo.SignBody
	out.PrepayId = rsp.Response.PrepayId
	return nil
}

// AliCallBack 支付宝支付回调
// https://opendocs.alipay.com/open/203/105286?pathHash=022a439c&ref=api
func (p *Pay) AliCallBack(req gopay.BodyMap, callFun func(order interface{}) error) error {
	orderId := req.Get("out_trade_no")
	//签名的验证
	if ok, err := alipay.VerifySign(p.cfg.Al.PublicKey, req); err != nil {
		return err
	} else if !ok {
		return err
	}
	//回调通知
	notify := &QueryNotify{
		OrderId:     orderId,
		TradeStatus: req.GetString("trade_status"),
		CallBody:    toJson(req),
	}

	if err := callFun(notify); err != nil {
		return err
	}
	return nil
}

// WxCallBack 微信支付回调
// V3ParseNotify 直接回调参数的解析处理，调用此方法来处理
// https://pay.weixin.qq.com/docs/merchant/apis/native-payment/payment-notice.html
// {
// "code": "FAIL", SUCCESS为清算机构接收成功
// "message": "失败"
// }
// 接收成功： HTTP应答状态码需返回200或204，无需返回应答报文。
// 接收失败： HTTP应答状态码需返回5XX或4XX，同时需返回应答报文，格式如下：
func (p *Pay) WxCallBack(req *wechat.V3NotifyReq, callFun func(order interface{}) error) error {
	result, err := req.DecryptCipherText(p.cfg.Wx.ApiV3Key)
	if err != nil {
	}
	//签名的验证
	keyMap := p.WP.WxPublicKeyMap()
	if err := req.VerifySignByPKMap(keyMap); err != nil {
		return err
	}
	//回调通知
	notify := &QueryNotify{
		OrderId:     result.OutTradeNo,
		TradeStatus: result.TradeState,
		CallBody:    toJson(req),
	}
	if err := callFun(notify); err != nil {
		return err
	}
	return nil
}

// QueryPayOrder 查询支付结果订单
func (p *Pay) QueryPayOrder(ctx context.Context, payType, orderId string) (*QueryNotify, error) {
	rs := &QueryNotify{
		OrderId: orderId,
	}
	if payType == PayAli {

		bm := make(gopay.BodyMap)
		bm.Set("out_trade_no", orderId)
		alRs, err := p.AP.TradeQuery(ctx, bm)
		if err != nil {
			return rs, err
		}
		rs.TradeStatus = alRs.Response.TradeStatus
		rs.CallBody = toJson(alRs)
		return rs, nil
	}

	if payType == PayWx {
		rsp, err := p.WP.V3TransactionQueryOrder(ctx, wechat.OrderNoType(2), orderId)
		if err != nil {
			return rs, err
		}
		rs.TradeStatus = rsp.Response.TradeState
		rs.CallBody = toJson(rsp)
		return rs, nil
	}
	return rs, nil
}

// WxTradeHandle 微信支付回调的状态处理
//func (p *Pay) WxTradeHandle(ctx context.Context, order *accModel.AccountOrder, nof *QueryNotify) error {
//
//	if order.OrderStatus == accModel.OrderSuccess {
//		return nil
//	}
//
//	/*
//		SUCCESS：支付成功
//		REFUND：转入退款
//		NOTPAY：未支付
//		CLOSED：已关闭
//		REVOKED：已撤销（付款码支付）
//		USERPAYING：用户支付中（付款码支付）
//		PAYERROR：支付失败(其他原因，如银行返回失败)
//	*/
//	tradeStatus := nof.TradeStatus
//	if tradeStatus == "NOTPAY" || tradeStatus == "USERPAYING" {
//		logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		if nof.TimeOutCheck && tradeStatus == "NOTPAY" && order.HasTimeOut(global.GetCfg().PayConf.ExpTime+300) {
//			if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderOut, order.OrderStatus, nof.CallBody); err != nil {
//				logs.Qezap.Info("wxPayCall", zap.Any("info", "交易结束，不可退款，交易支付成功"), zap.Any("orderId", order.OrderId), zap.Error(err))
//				return err
//			}
//		}
//		return nil
//	}
//	if tradeStatus == "REVOKED" || tradeStatus == "PAYERROR" {
//		logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付失败：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderFail, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("wxPayCall", zap.Any("info", "交易结束，不可退款，交易支付成功"), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	if tradeStatus == "REFUND" {
//		logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付失败：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderRefund, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	if tradeStatus == "CLOSED" {
//		logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付失败：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderOut, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	if tradeStatus == "SUCCESS" {
//		logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付成功：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderSuccess, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("wxPayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	return nil
//}
//
//// AliTradeHandle 阿里支付回调的状态处理
//func (svc *AccService) AliTradeHandle(ctx context.Context, order *accModel.AccountOrder, nof *QueryNotify) error {
//
//	if order.OrderStatus == accModel.OrderSuccess {
//		//表示已支付成功
//		return nil
//	}
//
//	/*
//		WAIT_BUYER_PAY    交易创建，等待买家付款。
//		TRADE_CLOSED      未付款交易超时关闭，或支付完成后全额退款。
//		TRADE_SUCCESS     交易支付成功。
//		TRADE_FINISHED    交易结束，不可退款。
//	*/
//	tradeStatus := nof.TradeStatus
//	if tradeStatus == "WAIT_BUYER_PAY" {
//		logs.Qezap.Info("alipayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		if nof.TimeOutCheck && order.HasTimeOut(global.GetCfg().PayConf.ExpTime+300) {
//			if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderOut, order.OrderStatus, nof.CallBody); err != nil {
//				logs.Qezap.Info("wxPayCall", zap.Any("info", "交易结束，不可退款，交易支付成功"), zap.Any("orderId", order.OrderId), zap.Error(err))
//				return err
//			}
//		}
//		return nil
//	}
//	if tradeStatus == "TRADE_CLOSED" {
//		logs.Qezap.Info("alipayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付失败：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderFail, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("alipayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	if tradeStatus == "TRADE_SUCCESS" || tradeStatus == "TRADE_FINISHED" {
//		logs.Qezap.Info("alipayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId))
//		//修改为支付成功：
//		if err := dao.Dao.OrderByStatus(ctx, order.Id, accModel.OrderSuccess, order.OrderStatus, nof.CallBody); err != nil {
//			logs.Qezap.Info("alipayCall", zap.Any("info", tradeStatus), zap.Any("orderId", order.OrderId), zap.Error(err))
//			return err
//		}
//		return nil
//	}
//	return nil
//}

func toJson(obj interface{}) string {
	b, _ := json.Marshal(obj)
	return string(b)
}

func timeAddAndFormat(add int64) string {
	return time.Now().Add(time.Duration(add) * time.Second).Format("2006-01-02 15:04:05")
}

func timeAddAndFormatWx(add int64) string {
	return time.Now().Add(time.Duration(add) * time.Second).Format("2006-01-02T15:04:05")
}
